package com.google.code.p.narcissus.core;

import java.awt.Dimension;
import java.awt.MouseInfo;
import java.awt.Point;

import nl.bitwalker.useragentutils.Browser;
import nl.bitwalker.useragentutils.Manufacturer;
import nl.bitwalker.useragentutils.UserAgent;

import com.thoughtworks.selenium.Selenium;

/**
 * Static methods allowing getting and setting browser configuration used by the Selenium session when writing tests.
 * 
 * @Author Jérôme JADOULLE [JADOJ] - 29 juil. 2010
 */
public class BrowserConfiguration {

	public enum WindowSize {
		Size_640x480, Size_800x600, Size_1024x768, Size_1280x1024, Size_1920x1200, Size_2048x1080, Size_2048x1536
	}

	/**
	 * Only the ones we think might be used at some point are recognized here. To know all of them, see wikipedia here: <code>http://fr.wikipedia.org/wiki/User-Agent</code>
	 */
	public static Browser getBrowser(Selenium session) {
		return UserAgent.parseUserAgentString(session.getEval("navigator.userAgent")).getBrowser();
	}

	public static boolean isIE(Selenium session) {
		return Browser.IE.equals(getBrowser(session));
	}

	public static boolean isFirefox(Selenium session) {
		return Browser.FIREFOX.equals(getBrowser(session));
	}

	public static boolean isOpera(Selenium session) {
		return Browser.OPERA.equals(getBrowser(session));
	}

	/**
	 * Allows setting the browser window to a specific size.
	 * <p>
	 * Only standard values are provided here, through the user of the <code>size</code> enumerated parameter.
	 * </p>
	 */
	public static void setWindowSize(Selenium session, WindowSize size) {
		String width, height;

		switch (size) {
		case Size_640x480:
			width = "640";
			height = "480";
			break;
		case Size_800x600:
			width = "800";
			height = "600";
			break;
		case Size_1280x1024:
			width = "1280";
			height = "1024";
			break;
		case Size_1920x1200:
			width = "1920";
			height = "1200";
			break;
		case Size_2048x1080:
			width = "2048";
			height = "1080";
			break;
		case Size_2048x1536:
			width = "2048";
			height = "1536";
			break;
		default:
			width = "1024";
			height = "768";
		}

		session.getEval("window.moveTo(0,0);window.resizeTo(" + width + " ," + height + ")");
	}

	/**
	 * Gets the size of the Browser window.
	 * <p>
	 * Browser window is the application window => it contains the back / next buttons, the toolbars and, of course, the document window.
	 * </p>
	 * <p>
	 * Note: At time of writing (2010/10/27) this is not part of the W3C DOM. It's available in FireFox, Opera and other major browsers EXCEPT IE (support starts from IE9 only)<br />
	 * As a fallback, when using IE prior to version 9, the size of the document will be sent instead of the size of the browser window.
	 * </p>
	 */
	public static Dimension getWindowSize(Selenium session) {
		if (Manufacturer.MICROSOFT.equals(getBrowser(session).getManufacturer())) {
			if (!isIE9(getBrowser(session))) {
				// In this case, it is not possible to get the exact size of the browser window. We will just fall back to the size of the document... that's
				// already something
				return getDocumentSize(session);
			}
		}

		// Starting IE9 (and all the other major browsers we will probably use), it is possible to get the size of the browser window.
		return new Dimension(new Integer(session.getEval("window.outerWidth")).intValue(), new Integer(session.getEval("window.outerHeight")).intValue());
	}

	private static boolean isIE9(Browser browser) {
		return Manufacturer.MICROSOFT.equals(browser.getManufacturer()) && browser.getId() == 90;
	}

	public static Dimension getDocumentSize(Selenium session) {
		int documentHeight = getMaxValue(evaluateAsInt(session, "document.body.scrollHeight"), evaluateAsInt(session, "document.documentElement.scrollHeight"),
		    evaluateAsInt(session, "document.body.offsetHeight"), evaluateAsInt(session, "document.documentElement.offsetHeight"),
		    evaluateAsInt(session, "document.body.clientHeight"), evaluateAsInt(session, "document.documentElement.clientHeight"), evaluateAsInt(session, "window.innerHeight"));

		int documentWidth = getMaxValue(evaluateAsInt(session, "document.body.scrollWidth"), evaluateAsInt(session, "document.documentElement.scrollWidth"),
		    evaluateAsInt(session, "document.body.offsetWidth"), evaluateAsInt(session, "document.documentElement.offsetWidth"), evaluateAsInt(session, "document.body.clientWidth"),
		    evaluateAsInt(session, "document.documentElement.clientWidth"), evaluateAsInt(session, "window.innerWidth"));

		return new Dimension(documentWidth, documentHeight);
	}

	private static int evaluateAsInt(Selenium session, String queryString) {
		return Integer.parseInt(session.getEval(queryString));
	}

	private static int getMaxValue(int... values) {
		int max = Integer.MIN_VALUE;
		for (int currentValue : values) {
			max = Math.max(max, currentValue);
		}

		return max;
	}

	/**
	 * <p>
	 * Uses various tricks to compute document's window coordinates. these are necessary to have the screenshots of the desired elements taken correctly.
	 * </p>
	 * 
	 * @throws InterruptedException
	 */
	public static BrowserCoordinates getBrowserCoordinates(Selenium session) throws NarcissusException {
		UserAgent userAgent = UserAgent.parseUserAgentString(session.getEval("navigator.userAgent"));

		boolean isLegacyFirefox = Browser.FIREFOX.equals(userAgent.getBrowser())
		    && Integer.valueOf(userAgent.getBrowserVersion().getVersion().replaceAll("\\.", "").substring(0, 2)) > 36;
		System.out.println("Is legacy Firefox browser? " + isLegacyFirefox);

		String[] coordinates = session.getEval( //
		    "(function(){var de=window.document.documentElement;" + "return " + (Browser.IE.equals(getBrowser(session)) ? "window.screenLeft+';'+window.screenTop+';'+" : // screen coords top left
		        // corner of window
		        // content area directly if IE/Opera
		        (!isLegacyFirefox ? "window.mozInnerScreenX+';'+window.mozInnerScreenY+';'" : // new equivalent properties in FF 3.6+
		            /* others */"window.screenX+';'+window.screenY+';'+")) + // outer corner of windowframe if FF<3.6/Chrome/Safari
		        "+de.clientWidth+';'+de.clientHeight})()").split(";", -1); // window content/view area size
		int winX = Integer.valueOf(coordinates[0]); // x coordinate of window view area OR window frame
		int winY = Integer.valueOf(coordinates[1]); // y coordinate of window view area OR window frame
		int winW = Integer.valueOf(coordinates[2]); // width of window view area
		int winH = Integer.valueOf(coordinates[3]); // height of window view area
		if (!Browser.IE.equals(getBrowser(session)) && isLegacyFirefox) { // our window coordinates are for the window _frame_,
			// whereas we need those of the window content (view) area
			// use a trick: calculate the offset from a mouse event which we generate using Robot
			session.getEval( // install a temporary "mousemove" event handler on the document
			    "window.theTempMouseHandler=function(e){"
			        + // define event handler function
			        "	storedVars['theWinContentX']=e.screenX-e.clientX;"
			        + // difference between screen coordinate and window coordinate gives window pos
			        "	storedVars['theWinContentY']=e.screenY-e.clientY;" + "	window."
			        + (Browser.IE.equals(getBrowser(session)) ? "detachEvent('onmousemove',this)" : "removeEventListener('mousemove',this,false)};")
			        + // remove handler after one hit
			        "window."
			        + (Browser.IE.equals(getBrowser(session)) ? "attachEvent('onmousemove',window.theTempMouseHandler)"
			            : "addEventListener('mousemove',window.theTempMouseHandler,false)")); // install
			// the
			// created
			// event
			// handler
			// now we need to move the mousepointer across the window, in order to trigger the mousemove event

			Point mouseHere;
			mouseHere = MouseInfo.getPointerInfo().getLocation(); // save starting position

			ScreenCaptureRobot robot = new ScreenCaptureRobot();
			robot.mouseMove(0, 0); // move to top left corner of screen. NOTE: the window was positioned here at start
			robot.mouseMove(winH / 2, winW / 2); // move into, then out of window
			robot.mouseMove(mouseHere.x, mouseHere.y); // move back to saved position
			try {
				// give javascript time to finish
				Thread.sleep(20);
			} catch (InterruptedException e) {
				throw new NarcissusException(BrowserConfiguration.class.getName() + ".imposssibleToGetDocumentCoordinatesEx", e);
			}
			// now we retrieve the values stored by the event handler
			coordinates = session.getEval("storedVars['theWinContentX']+';'+storedVars['theWinContentY']").split(";", -1);
			winX = Integer.valueOf(coordinates[0]); // x coordinate of window view area
			winY = Integer.valueOf(coordinates[1]); // y coordinate of window view area
		}

		return new BrowserCoordinates(winX, winY, winW, winH);
	}

	public static class BrowserCoordinates {
		public int winX, winY, winWidth, winHeight;

		public BrowserCoordinates(int winX, int winY, int winWidth, int winHeight) {
			this.winX = winX;
			this.winY = winY;
			this.winWidth = winWidth;
			this.winHeight = winHeight;
		}
	}

}